#ifndef MY_STRBLOB_H
#define MY_STRBLOB_H

#include <vector>
#include <string>
#include <memory>
#include <initializer_list>
#include <stdexcept>

using namespace std;

class StrBlobPtr;

class StrBlob {
    friend class StrBlobPtr;
public:
    typedef std::vector<std::string>::size_type size_type;
    StrBlob();
    StrBlob(std::initializer_list<std::string> il);
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const std::string &t) { data->push_back(t); }
    void pop_back();
    std::string& front();
    const std::string& front() const;
    std::string& back();
    const std::string& back() const;
    // 必须定义 StrBlobPtr 之后才能定义这两个函数
    // 提供给 StrBlobPtr 的接口
    StrBlobPtr begin();
    StrBlobPtr end();
    StrBlobPtr begin() const;
    StrBlobPtr end() const;
private:
    std::shared_ptr<std::vector<std::string>> data;
    void check(size_type i, const std::string &msg) const;
};

StrBlob::StrBlob()
    : data(std::make_shared<std::vector<std::string>>()) { }

StrBlob::StrBlob(std::initializer_list<std::string> il)
    : data(std::make_shared<std::vector<std::string>>(il)) { }

void StrBlob::check(size_type i, const std::string &msg) const
{
    if (i >= data->size()) {
        throw std::out_of_range(msg);
    }
}

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

std::string& StrBlob::front()
{
    check(0, "front on empty StrBlob");
    return data->front();
}

const std::string& StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->front();
}

std::string& StrBlob::back()
{
    check(0, "back on empty StrBlob");
    return data->back();
}

const std::string& StrBlob::back() const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

// 当试图访问一个不存在的元素时， StrBlobPtr 抛出一个异常
class StrBlobPtr {
    friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:
    StrBlobPtr() : curr(0) { }
    StrBlobPtr(StrBlob &a, std::size_t sz = 0)
        : wptr(a.data), curr(sz) { }
    StrBlobPtr(const StrBlob &a, size_t sz = 0)
        : wptr(a.data), curr(sz) { }
    std::string& deref() const;
    StrBlobPtr& incr();  // 前缀递增
    StrBlobPtr& decr();  // 前缀递减
private:
    // 若检查成功，check 返回一个指向 vector 的 shared_ptr
    std::shared_ptr<std::vector<std::string>>
        check(std::size_t, const std::string&) const;
    // 保存一个 weak_ptr，意味着底层 vector 可能会被销毁
    std::weak_ptr<std::vector<std::string>> wptr;
    std::size_t curr;  // 在数组中的当前位置
};

shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();  // vector 还存在么
    if (!ret) {
        throw runtime_error("unbound StrBolbPtr.");
    }
    if (i >= ret->size()) {
        throw out_of_range(msg);
    }
    return ret;  // 否则，返回指向 vector 的 shared_ptr
}

string& StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end.");
    return (*p)[curr];  // (*p) 是对象所指向的 vector
}

// 前缀递增：返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr()
{
    // 如果 curr 已经指向容器的尾后位置，就不能再递增它
    check(curr, "increment past end of StrBlobPtr.");
    ++curr;  // 推进当前位置
    return *this;
}

// 前缀递减：返回递减后的对象的引用
StrBlobPtr&StrBlobPtr::decr()
{
    // 如果 --curr 已经为 0，递减它就会产生一个非法下标
    --curr;  // 递减当前位置
    check(-1, "decrement past begin of StrBlobPtr.");
    return *this;
}

// StrBlob 的 begin 和 end 成员定义
StrBlobPtr StrBlob::begin()
{
    return StrBlobPtr(*this);
}

StrBlobPtr StrBlob::end()
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

StrBlobPtr StrBlob::begin() const
{
    return StrBlobPtr(*this);
}

StrBlobPtr StrBlob::end() const
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

// StrBlobPtr 的比较操作
bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
    auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
    // 若底层 vector 是同一个
    if (l == r) {
        // 则两个指针都是空，或者指向相同元素时，它们相等
        return (!r || lhs.curr == rhs.curr);
    } else {
        return false;  // 若指向不同 vector, 则不可能相等
    }
}

bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
    return !eq(lhs, rhs);
}

#endif // MY_STRBLOB_H
